package com.iogogogo.activity.util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.BeanUtils;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.util.CollectionUtils;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * Created by tao.zeng on 2022/6/16.
 */
@Slf4j
public class ExtendBeanUtils extends BeanUtils {

    /**
     * 转换对象
     *
     * @param <S>              源对象类型
     * @param <T>              目标对象类型
     * @param source           源对象
     * @param targetSupplier   目标对象供应方
     * @param ignoreProperties 忽略属性
     * @return 目标对象 t
     */
    public static <S, T> T convertObj(S source, Supplier<T> targetSupplier, final String... ignoreProperties) {
        return convertObj(source, targetSupplier, null, ignoreProperties);
    }

    /**
     * 转换对象
     *
     * @param <S>              源对象类型
     * @param <T>              目标对象类型
     * @param source           源对象
     * @param targetSupplier   目标对象供应方
     * @param callback         回调方法
     * @param ignoreProperties 忽略属性
     * @return 目标对象 t
     */
    public static <S, T> T convertObj(S source, Supplier<T> targetSupplier, ConvertCallback<S, T> callback, final String... ignoreProperties) {
        if (Objects.isNull(source) || Objects.isNull(targetSupplier)) return null;
        T target = targetSupplier.get();
        BeanUtils.copyProperties(source, target, ignoreProperties);
        if (callback != null) callback.onCallback(source, target);
        return target;
    }

    /**
     * 转换集合对象
     *
     * @param <S>              源对象类型
     * @param <T>              目标对象类型
     * @param sources          源对象list
     * @param targetSupplier   目标对象供应方
     * @param ignoreProperties 忽略属性
     * @return 目标对象list list
     */
    public static <S, T> List<T> convertList(List<S> sources, Supplier<T> targetSupplier, final String... ignoreProperties) {
        return convertList(sources, targetSupplier, null, ignoreProperties);
    }

    /**
     * 转换集合对象
     *
     * @param <S>              源对象类型
     * @param <T>              目标对象类型
     * @param sources          源对象list
     * @param targetSupplier   目标对象供应方
     * @param callback         回调方法
     * @param ignoreProperties 忽略属性
     * @return 目标对象list list
     */
    public static <S, T> List<T> convertList(List<S> sources, Supplier<T> targetSupplier, ConvertCallback<S, T> callback, final String... ignoreProperties) {
        if (CollectionUtils.isEmpty(sources) || Objects.isNull(targetSupplier)) return null;
        return sources.stream().map(source -> {
            T target = targetSupplier.get();
            BeanUtils.copyProperties(source, target, ignoreProperties);
            if (callback != null) callback.onCallback(source, target);
            return target;
        }).collect(Collectors.toList());
    }

    /**
     * 回调接口
     *
     * @param <S> 源对象类型
     * @param <T> 目标对象类型
     */
    @FunctionalInterface
    public interface ConvertCallback<S, T> {
        /**
         * On callback.
         *
         * @param source the source
         * @param target the target
         */
        void onCallback(S source, T target);
    }

    /**
     * Trans bean 2 map map.
     * <p>
     * bean 转 map 对象
     *
     * @param obj              源bean对象
     * @param ignoreProperties 忽略属性
     * @return bean转map结果 map
     */
    public static Map<String, Object> beanToMapForJavaAPI(Object obj, String... ignoreProperties) {
        if (Objects.isNull(obj)) return null;
        Map<String, Object> map = new HashMap<>();
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            Set<String> collect = Stream.of(ignoreProperties).collect(Collectors.toSet());
            for (PropertyDescriptor property : propertyDescriptors) {
                String key = property.getName();
                // 过滤class属性
                if (!key.equals("class")) {
                    // 忽略不需要映射的属性
                    if (!CollectionUtils.isEmpty(collect) && collect.contains(key)) continue;
                    // 得到property对应的getter方法
                    Method getter = property.getReadMethod();
                    Object value = getter.invoke(obj);
                    map.put(key, value);
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return map;
    }

    /**
     * Trans bean 2 map map.
     * <p>
     * bean 转 map 对象
     *
     * @param bean             源bean对象
     * @param ignoreProperties 忽略属性
     * @return bean转map结果 map
     */
    public static <T> Map<String, Object> beanToMapForSpringAPI(T bean, String... ignoreProperties) {
        Map<String, Object> map = new HashMap<>();
        if (Objects.nonNull(bean)) {
            BeanMap beanMap = BeanMap.create(bean);
            Set<String> collect = Stream.of(ignoreProperties).collect(Collectors.toSet());
            Set<?> keySet = beanMap.keySet();
            for (Object key : keySet) {
                String k = String.valueOf(key);
                if (!CollectionUtils.isEmpty(collect) && collect.contains(k)) continue;
                map.put(k, beanMap.get(key));
            }
        }
        return map;
    }

    public static <T> T mapToBean(Map<?, ?> map, Class<T> clazz) {
        try {
            T bean = instantiateClass(clazz);
            BeanMap beanMap = BeanMap.create(bean);
            beanMap.putAll(map);
            return bean;
        } catch (BeanInstantiationException e) {
            log.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 行转列
     *
     * @param <T>   the type parameter
     * @param clazz 源bean类型
     * @param list  bean类型集合
     * @return 行转列结果 list
     */
    public static <T> List<List<Object>> lineToColumn(Class<T> clazz, List<T> list) {
        Field[] fields = clazz.getDeclaredFields();
        List<List<Object>> result = IntStream.range(0, fields.length)
                .mapToObj(x -> new ArrayList<>())
                .collect(Collectors.toList());
        list.forEach(x -> {
            for (int i = 0; i < fields.length; i++) {
                Field field = fields[i];
                field.setAccessible(true);
                try {
                    result.get(i).add(field.get(x));
                } catch (IllegalAccessException e) {
                    log.error(e.getMessage(), e);
                }
            }
        });
        return result;
    }
}
